package com.szcinda.service.dto.containerOrder;

import com.szcinda.repository.*;
import com.szcinda.service.PageResult;
import com.szcinda.service.SnowFlakeFactory;
import com.szcinda.service.TypeStringUtils;
import com.szcinda.service.dto.idGenerator.IdGeneratorService;
import com.szcinda.service.dto.log.LogCreateDto;
import com.szcinda.service.dto.log.LogService;
import com.szcinda.service.dto.organization.OrganizationService;
import com.szcinda.service.dto.role.RoleRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Transactional
public class ContainerOrderServiceImpl implements ContainerOrderService {

    private final ContainerOrderRepository containerOrderRepository;
    private final ClientRepository clientRepository;
    private final SnowFlakeFactory snowFlakeFactory;
    private final UserRepository userRepository;
    private final OrganizationService organizationService;
    private final OrderFollowRepository orderFollowRepository;
    private final ZadOrderRepository zadOrderRepository;
    private final LogService logService;
    private final DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMM");
    private final IdGeneratorService idGeneratorService;
    private final RoleRepository roleRepository;

    public ContainerOrderServiceImpl(ContainerOrderRepository containerOrderRepository, ClientRepository clientRepository,
                                     UserRepository userRepository, OrganizationService organizationService,
                                     OrderFollowRepository orderFollowRepository, ZadOrderRepository zadOrderRepository,
                                     LogService logService, IdGeneratorService idGeneratorService, RoleRepository roleRepository) {
        this.containerOrderRepository = containerOrderRepository;
        this.clientRepository = clientRepository;
        this.userRepository = userRepository;
        this.organizationService = organizationService;
        this.orderFollowRepository = orderFollowRepository;
        this.zadOrderRepository = zadOrderRepository;
        this.logService = logService;
        this.idGeneratorService = idGeneratorService;
        this.roleRepository = roleRepository;
        this.snowFlakeFactory = SnowFlakeFactory.getInstance();
    }


    @Override
    public void create(ContainerOrderCreateDto createDto) {
        ContainerOrder record = containerOrderRepository.findFirstByCabinetNumber(createDto.getCabinetNumber());
        Assert.isTrue(record == null, String.format("存在柜号【%s】记录", createDto.getCabinetNumber()));
        ContainerOrder order = new ContainerOrder();
        BeanUtils.copyProperties(createDto, order);
        order.setId(snowFlakeFactory.nextId("CO"));
        if (order.getPickDate() != null && order.getLessReturnDays() > 0) {
            order.setReturnDate(order.getPickDate().plusDays(Math.max(order.getLessReturnDays() - 3, 0)));
        }
        User user = userRepository.findById(createDto.getOpId());
        order.setUserId(user.getId());
        order.setUserName(user.getNickName());
        if (!StringUtils.isEmpty(order.getActReturnDate())) {
            order.setPackageType(TypeStringUtils.packageType2);
        } else {
            order.setPackageType(TypeStringUtils.packageType1);
        }
        // 计算超出天数
        if (order.getPreReturnDate() != null && order.getActReturnDate() != null && order.getPreReturnDate().isBefore(order.getActReturnDate())) {
            order.setBeyondDays(Period.between(order.getPreReturnDate(), order.getActReturnDate()).getDays());
        }
        Client client = clientRepository.findById(createDto.getClientId());
        // 获取今天共有多少单
        LocalDate now = LocalDate.now();
        Specification<ContainerOrder> specification = ((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            Predicate timeStart = criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), now.atStartOfDay());
            predicates.add(timeStart);
            Predicate timeEnd = criteriaBuilder.lessThan(root.get("createTime"), now.plusDays(1).atStartOfDay());
            predicates.add(timeEnd);
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        });
        long count = containerOrderRepository.count(specification);
        order.setBusinessNumber(client.getCode() + df.format(LocalDateTime.now()) + String.format("%04d", count + 1));
        Organization organization = organizationService.getById(createDto.getOrganizationId());
        order.setOrganizationId(organization.getId());
        order.setOrganizationName(organization.getName());
        containerOrderRepository.save(order);
        LogCreateDto logCreateDto = new LogCreateDto(user.getId(), user.getNickName(), "创建集装箱业务", order.getBusinessNumber());
        logService.create(logCreateDto);
    }

    @Override
    public void update(ContainerOrderUpdateDto updateDto) {
        ContainerOrder order = containerOrderRepository.findById(updateDto.getId());
        BeanUtils.copyProperties(updateDto, order, "id");
        if (order.getPickDate() != null && order.getLessReturnDays() > 0) {
            order.setReturnDate(order.getPickDate().plusDays(Math.max(order.getLessReturnDays() - 3, 0)));
        }
        if (order.getActReturnDate() != null) {
            order.setPackageType(TypeStringUtils.packageType2);
        }
        // 计算超出天数
        if (order.getPreReturnDate() != null && order.getActReturnDate() != null && order.getPreReturnDate().isBefore(order.getActReturnDate())) {
            order.setBeyondDays(Period.between(order.getPreReturnDate(), order.getActReturnDate()).getDays());
            if (order.getBeyondDays() < 0) {
                order.setBeyondDays(0);
            }
        }
        // 更新集装箱还箱日期
        if (StringUtils.hasText(order.getOrderId())) {
            ZadOrder zadOrder = zadOrderRepository.findById(order.getOrderId());
            zadOrder.setPackageReturnDate(updateDto.getActReturnDate());
            zadOrder.setPackageUseDays(order.getUseDays());
            zadOrder.setOrderBeyondDays((int) order.getBeyondDays());
            zadOrderRepository.save(zadOrder);
        }
        Organization organization = organizationService.getById(updateDto.getOrganizationId());
        order.setOrganizationId(organization.getId());
        order.setOrganizationName(organization.getName());
        containerOrderRepository.save(order);
        User user = userRepository.findById(updateDto.getOpId());
        LogCreateDto logCreateDto = new LogCreateDto(user.getId(), user.getNickName(), "修改集装箱业务", order.getBusinessNumber());
        logService.create(logCreateDto);
    }

    @Override
    public PageResult<ContainerOrderDto> query(ContainerOrderQueryDto params) {
        Assert.hasText(params.getUserId(), "当前操作用户参数必填");
        Specification<ContainerOrder> specification = ((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            User user = userRepository.findById(params.getUserId());
            Role role = roleRepository.findById(user.getRoleId());
            Predicate organizationId = criteriaBuilder.equal(root.get("organizationId"), params.getOrganizationId());
            predicates.add(organizationId);
            boolean isAdmin = RoleType.SUPER_ADMIN.equals(role.getName()) || RoleType.ADMIN.equals(role.getName()) || role.isEqualAdmin();
            if (!isAdmin) {
                // 如果不是管理员，只能看到自己的
                Predicate userId = criteriaBuilder.equal(root.get("userId"), params.getUserId());
                predicates.add(userId);
            }
            if (!StringUtils.isEmpty(params.getBusinessNumber())) {
                Predicate site = criteriaBuilder.like(root.get("businessNumber"), "%" + params.getBusinessNumber().trim() + "%");
                predicates.add(site);
            }
            if (!StringUtils.isEmpty(params.getClientId())) {
                Predicate clientId = criteriaBuilder.equal(root.get("clientId"), params.getClientId());
                predicates.add(clientId);
            }
            if (!StringUtils.isEmpty(params.getCabinetNumber())) {
                Predicate site = criteriaBuilder.like(root.get("cabinetNumber"), "%" + params.getCabinetNumber().trim() + "%");
                predicates.add(site);
            }
            if (params.getCreateTimeStart() != null) {
                Predicate timeStart = criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), params.getCreateTimeStart().atStartOfDay());
                predicates.add(timeStart);
            }
            if (params.getCreateTimeEnd() != null) {
                Predicate timeEnd = criteriaBuilder.lessThan(root.get("createTime"), params.getCreateTimeEnd().plusDays(1).atStartOfDay());
                predicates.add(timeEnd);
            }
            if (StringUtils.hasText(params.getDaiBan())) {
                Predicate packageType = criteriaBuilder.equal(root.get("packageType"), TypeStringUtils.packageType1);
                predicates.add(packageType);
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        });
        Sort sort = new Sort(Sort.Direction.DESC, "createTime", "businessNumber");
        Pageable pageable = new PageRequest(params.getPage() - 1, params.getPageSize(), sort);
        Page<ContainerOrder> details = containerOrderRepository.findAll(specification, pageable);
        List<ContainerOrderDto> dtos = new ArrayList<>();
        List<String> clientIds = details.getContent().stream().map(ContainerOrder::getClientId).collect(Collectors.toList());
        List<String> orderIds = details.getContent().stream().filter(containerOrder -> StringUtils.hasText(containerOrder.getOrderId())).map(ContainerOrder::getOrderId).collect(Collectors.toList());
        List<Client> clients = clientRepository.findAll(clientIds);
        LocalDate now = LocalDate.now();
        List<ZadOrder> zadOrders = zadOrderRepository.findByIdIn(orderIds);
        details.getContent().forEach(containerOrder -> {
            ContainerOrderDto dto = new ContainerOrderDto();
            BeanUtils.copyProperties(containerOrder, dto);
            // 查看是否封账
            zadOrders.stream().filter(zadOrder -> zadOrder.getId().equals(containerOrder.getOrderId()))
                    .findFirst()
                    .ifPresent(zadOrder -> {
                        if (BillType.STATUS_YJS.equals(zadOrder.getSettlement())) {
                            dto.setLock(true);
                        }
                    });
            if (dto.getActReturnDate() != null) {
                dto.setPackageTypeName(TypeStringUtils.packageTypeName2);
                dto.setPackageType(TypeStringUtils.packageType2);
                containerOrder.setPackageType(TypeStringUtils.packageType2);
            } else {
                dto.setPackageTypeName(TypeStringUtils.packageTypeName1);
                dto.setPackageType(TypeStringUtils.packageType1);
                containerOrder.setPackageType(TypeStringUtils.packageType1);
            }
            dto.setLessReturnDays(0);
            dto.setBeyondDays(0);
            // 如果还箱日期空值，剩余还箱天数=预计还箱天数-当前时间
            if (dto.getActReturnDate() == null && dto.getPreReturnDate() != null) {
                // 计算剩余还箱天数
                long days = Math.abs(containerOrder.getPreReturnDate().until(now, ChronoUnit.DAYS));
                if (containerOrder.getPreReturnDate().isBefore(now)) {
                    dto.setLessReturnDays(-days);
                } else {
                    dto.setLessReturnDays(days);
                }
            }
            // 如果还箱日期不为空，剩余还箱天数=预计还箱天数-还箱天数
            if (dto.getActReturnDate() != null && dto.getPreReturnDate() != null) {
                // 计算剩余还箱天数
                long days = Math.abs(containerOrder.getPreReturnDate().until(dto.getActReturnDate(), ChronoUnit.DAYS));
                if (containerOrder.getPreReturnDate().isBefore(dto.getActReturnDate())) {
                    dto.setLessReturnDays(-days);
                } else {
                    dto.setLessReturnDays(days);
                }
            }
            // 如果预计还箱日期在实际还箱日期之前，计算超期天数
            if (dto.getPreReturnDate() != null && dto.getActReturnDate() != null) {
                long days = containerOrder.getPreReturnDate().until(dto.getActReturnDate(), ChronoUnit.DAYS);
                dto.setBeyondDays(days);
                containerOrder.setBeyondDays(days);
                if (containerOrder.getBeyondDays() < 0) {
                    containerOrder.setBeyondDays(0);
                }
                containerOrderRepository.save(containerOrder);
            }
            // 计算实际用箱日期
            if (dto.getPickDate() != null && dto.getActReturnDate() != null) {
                long days = containerOrder.getPickDate().until(dto.getActReturnDate(), ChronoUnit.DAYS);
                dto.setUseDays(days);
            }
            clients.stream().filter(client -> client.getId().equals(containerOrder.getClientId()))
                    .findFirst()
                    .ifPresent(client -> dto.setClientName(client.getName()));
            dtos.add(dto);
        });
        return PageResult.of(dtos, params.getPage(), params.getPageSize(), details.getTotalElements());
    }


    @Override
    public List<ContainerOrderDto> queryAll(ContainerOrderQueryDto params) {
        Specification<ContainerOrder> specification = ((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            User user = userRepository.findById(params.getUserId());
            Role role = roleRepository.findById(user.getRoleId());
            if (RoleType.SUPER_ADMIN.equals(role.getName())) {
                // 得到所有的记录
                List<String> orgIds = organizationService.getAllOrgIds();
                Expression<String> exp = root.get("organizationId");
                predicates.add(exp.in(orgIds));
                // 组织机构ID
                if (StringUtils.hasText(params.getOrganizationId())) {
                    Predicate organizationId = criteriaBuilder.equal(root.get("organizationId"), params.getOrganizationId());
                    predicates.add(organizationId);
                }
            } else if (RoleType.ADMIN.equals(role.getName())) {
                // 得到当前机构及子机构的所有记录
                List<String> orgIds = organizationService.getChildOrgIds(user.getOrganizationId());
                Expression<String> exp = root.get("organizationId");
                predicates.add(exp.in(orgIds));
                // 组织机构ID
                if (StringUtils.hasText(params.getOrganizationId())) {
                    Predicate organizationId = criteriaBuilder.equal(root.get("organizationId"), params.getOrganizationId());
                    predicates.add(organizationId);
                }
            } else if (role.isEqualAdmin()) {
                // 如果授予了机构管理员权限
                List<String> orgIds = organizationService.getChildOrgIds(user.getOrganizationId());
                Expression<String> exp = root.get("organizationId");
                predicates.add(exp.in(orgIds));
                // 组织机构ID
                if (StringUtils.hasText(params.getOrganizationId())) {
                    Predicate organizationId = criteriaBuilder.equal(root.get("organizationId"), params.getOrganizationId());
                    predicates.add(organizationId);
                }
            } else {
                // 只查询当前机构的订单
                Predicate organizationId = criteriaBuilder.equal(root.get("organizationId"), params.getOrganizationId());
                predicates.add(organizationId);
                // 只能看到自己的订单
                Predicate userId = criteriaBuilder.equal(root.get("userId"), params.getUserId());
                predicates.add(userId);
            }
            if (!StringUtils.isEmpty(params.getBusinessNumber())) {
                Predicate site = criteriaBuilder.like(root.get("businessNumber"), "%" + params.getBusinessNumber().trim() + "%");
                predicates.add(site);
            }
            if (!StringUtils.isEmpty(params.getClientId())) {
                Predicate clientId = criteriaBuilder.equal(root.get("clientId"), params.getClientId());
                predicates.add(clientId);
            }
            if (!StringUtils.isEmpty(params.getCabinetNumber())) {
                Predicate site = criteriaBuilder.like(root.get("cabinetNumber"), "%" + params.getCabinetNumber().trim() + "%");
                predicates.add(site);
            }
            if (params.getCreateTimeStart() != null) {
                Predicate timeStart = criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), params.getCreateTimeStart().atStartOfDay());
                predicates.add(timeStart);
            }
            if (params.getCreateTimeEnd() != null) {
                Predicate timeEnd = criteriaBuilder.lessThan(root.get("createTime"), params.getCreateTimeEnd().plusDays(1).atStartOfDay());
                predicates.add(timeEnd);
            }
            if (StringUtils.hasText(params.getDaiBan())) {
                Predicate packageType = criteriaBuilder.equal(root.get("packageType").as(String.class), TypeStringUtils.packageType1);
                predicates.add(packageType);
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        });
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");
        List<ContainerOrder> containerOrders = containerOrderRepository.findAll(specification, sort);
        List<ContainerOrderDto> dtos = new ArrayList<>();
        List<String> clientIds = containerOrders.stream().map(ContainerOrder::getClientId).collect(Collectors.toList());
        List<Client> clients = clientRepository.findAll(clientIds);
        containerOrders.forEach(containerOrder -> {
            ContainerOrderDto dto = new ContainerOrderDto();
            BeanUtils.copyProperties(containerOrder, dto);
            if (TypeStringUtils.packageType1.equals(containerOrder.getPackageType())) {
                dto.setPackageTypeName(TypeStringUtils.packageTypeName1);
            } else if (TypeStringUtils.packageType2.equals(containerOrder.getPackageType())) {
                dto.setPackageTypeName(TypeStringUtils.packageTypeName2);
            }
            clients.stream().filter(client -> client.getId().equals(containerOrder.getClientId()))
                    .findFirst()
                    .ifPresent(client -> dto.setClientName(client.getName()));
            dtos.add(dto);
        });
        return dtos;
    }

    @Override
    public void returnPackage(List<String> ids) {
        List<ContainerOrder> orders = containerOrderRepository.findByIdIn(ids);
        orders.forEach(order -> {
            order.setPackageType(TypeStringUtils.packageType2);
        });
        containerOrderRepository.save(orders);
    }

    @Override
    public void delete(String id, String userId) {
        ContainerOrder containerOrder = containerOrderRepository.findById(id);
        containerOrderRepository.delete(containerOrder);
        User user = userRepository.findById(userId);
        LogCreateDto createDto = new LogCreateDto(user.getId(), user.getNickName(), "删除集装箱业务", containerOrder.getBusinessNumber());
        logService.create(createDto);
    }

    @Override
    public List<ContainerOrderDto> getUnReturnList(String userId, String orgId) {
        User user = userRepository.findById(userId);
        Role role = roleRepository.findById(user.getRoleId());
        List<String> childOrgIds = organizationService.getChildOrgIds(orgId);
        boolean isAdmin = RoleType.SUPER_ADMIN.equals(role.getName()) || RoleType.ADMIN.equals(role.getName()) || role.isEqualAdmin();
        List<ContainerOrder> orders = containerOrderRepository.findAllByPackageTypeOrPackageTypeIsNull(TypeStringUtils.packageType1);
        // 如果是管理员，能看到所有的
        if (isAdmin) {
            orders = orders.stream().filter(containerOrder -> childOrgIds.contains(containerOrder.getOrganizationId())).collect(Collectors.toList());
        } else {
            orders = orders.stream().filter(containerOrder -> containerOrder.getUserId().equals(userId)).collect(Collectors.toList());
        }
        orders = orders.stream().filter(order -> TypeStringUtils.packageType1.equals(order.getPackageType())).collect(Collectors.toList());
        List<ContainerOrderDto> dtos = new ArrayList<>();
        List<String> clientIds = orders.stream().map(ContainerOrder::getClientId).collect(Collectors.toList());
        List<Client> clients = clientRepository.findAll(clientIds);
        orders.forEach(containerOrder -> {
            ContainerOrderDto dto = new ContainerOrderDto();
            BeanUtils.copyProperties(containerOrder, dto);
            clients.stream().filter(client -> client.getId().equals(containerOrder.getClientId()))
                    .findFirst()
                    .ifPresent(client -> dto.setClientName(client.getName()));
            dtos.add(dto);
        });
        return dtos;
    }

    @Override
    public void batchCreate(List<ContainerOrderImportDto> importDatas, String opId) {
        User user = userRepository.findById(opId);
        List<Organization> organizations = organizationService.getAll();
        List<Client> clients = clientRepository.findAll();
        for (ContainerOrderImportDto importData : importDatas) {
            if (StringUtils.hasText(importData.getOrganizationName())) {
                boolean anyMatch = organizations.stream().anyMatch(organization -> organization.getName().equals(importData.getOrganizationName()));
                Assert.isTrue(anyMatch, "没有找到机构抬头为[" + importData.getOrganizationName() + "]的记录");
            }
            Assert.isTrue(StringUtils.hasText(importData.getClientName()), "客户简称不能为空");
            boolean anyMatch = clients.stream().anyMatch(client -> client.getName().equals(importData.getClientName()));
            Assert.isTrue(anyMatch, "没有找到客户简称为[" + importData.getOrganizationName() + "]的记录");
        }
        for (ContainerOrderImportDto importData : importDatas) {
            ContainerOrderCreateDto createDto = new ContainerOrderCreateDto();
            BeanUtils.copyProperties(importData, createDto);
            if (importData.getPickDate() != null) {
                Instant instant = importData.getPickDate().toInstant();
                ZoneId zoneId = ZoneId.systemDefault();
                // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime
                createDto.setPickDate(instant.atZone(zoneId).toLocalDate());
            }
            if (importData.getPreReturnDate() != null) {
                Instant instant = importData.getPreReturnDate().toInstant();
                ZoneId zoneId = ZoneId.systemDefault();
                // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime
                createDto.setPreReturnDate(instant.atZone(zoneId).toLocalDate());
            }
            if (importData.getActReturnDate() != null) {
                Instant instant = importData.getActReturnDate().toInstant();
                ZoneId zoneId = ZoneId.systemDefault();
                // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime
                createDto.setActReturnDate(instant.atZone(zoneId).toLocalDate());
            }
            organizations.stream().filter(organization -> organization.getName().equals(importData.getOrganizationName()))
                    .findFirst()
                    .ifPresent(organization -> createDto.setOrganizationId(organization.getId()));
            if (StringUtils.isEmpty(createDto.getOrganizationId())) {
                createDto.setOrganizationId(user.getOrganizationId());
            }
            clients.stream().filter(client -> client.getName().equals(importData.getClientName()))
                    .findFirst()
                    .ifPresent(client -> createDto.setClientId(client.getId()));
            createDto.setOpId(opId);
            this.create(createDto);
        }
    }
}
